#!/usr/bin/env python
'''\
wmlmove -- generate commands to move a unit in the Wesnoth tree

usage: wmlmove --help
       wmlmove [--revert] {src...} {dst}
       wmlmove --delete src

This script facilitates moving or deleting units (referenced by their
.cfg file) along with all of their associated resources (images and
sounds).  It may be run from the Wesnoth top-level directory or
anywhere beneath.

The source unit must be specified as a pathname beginning with either
data/ or a campaign subdirectory name.  The destination may be the
name of a campaign or the special name "core", indicating the images
and sounds directories in data/core. All resource files (images and
sounds) referenced in the unit .cfg and included near it move with it.

"Near" for a unit in the core data means anywhere in the core data,
but not in a campaign directory.  "Near" for a unit under a
data/campaign directory means anywhere in that same campaign
directory, but not in core data or any other campaign directory.

The script generates a sequence of shell commands.  These commands
will move the unit and all its resources are moved to appropriate
places under the target campaign or core directories.  Subversion move
commands will be used on version-controlled files.

The --revert option generates commands used to revert the result of a 
previous move, undoing version-control operations as needed.

Note: After performing a move generated with this command, use wmlscope
to check that you have not left any references to the moved units dangling.

Also be aware that this tool does nothing to update the unit
translation files.
''' 

import sys, os, time, re, getopt, sre_constants, md5
from wesnoth.wmltools import *

if __name__ == "__main__":
    # Process options.
    (options, arguments) = getopt.getopt(sys.argv[1:], "dhi:lLr", ['delete', 'help', 'imageclass', 'list', 'listspaces', 'revert'])
    listem = False
    listspaces = False
    iclass = None
    delete = False
    revert = False
    for (switch, val) in options:
        if switch in ('-h', '--help'):
            sys.stderr.write(__doc__)
            sys.exit(0)
        elif switch in ('-d', '--delete'):
            delete = True
        elif switch in ('-i', '--imageclass'):
            iclass = val
        elif switch in ('-l', '--list'):
            listem = True
        elif switch in ('-L', '--listspaces'):
            listspaces = True
        elif switch in ('-r', '--revert'):
            listem = True

    if not listspaces:
        if len(arguments) == 0:
            sys.stderr.write("wmlmove: at least one path to a unit is required.\n")
            sys.stderr.write(__doc__)
            sys.exit(1)
        if not delete:
            if len(arguments) == 1:
                sys.stderr.write("wmlmove: a campaign name or 'core' is required.\n")
                sys.stderr.write(__doc__)
                sys.exit(1)
            else:
                dst = arguments.pop()

    # First, pop upward to the top-level directory.
    upwards = os.getcwd().split(os.sep)
    upwards.reverse()
    for pathpart in upwards:
        if pathpart == "wesnoth":
            break
        else:
            os.chdir("..")
    else:
        sys.stderr.write("wmlmove: must be run from within a Battle "
                         "for Wesnoth source tree.\n")
        sys.exit(1)

    if listspaces:
        print " ".join(map(os.path.basename, scopelist()))
        sys.exit(0)

    # Locate the unit .cfgs to be moved.
    srclist = []
    for src in arguments:
        try:
            (namespace, resource) = src.split("::")
        except ValueError:
            sys.stderr.write("wmlmove: source name must be in the form "
                             "namespace::resource.\n")
            sys.exit(1)
        if not is_namespace(namespace):
            sys.stderr.write("wmlmove: no such scope as %s.\n" % namespace)
            sys.exit(1)
        src = resolve_unit_cfg(namespace, resource)
        if not os.path.exists(src):
            sys.stderr.write("wmlmove: can't find %s to move it.\n" % src)
            sys.exit(1)
        srclist.append(src)
  
    # Validate the destination.
    if not delete:
        dstdir = namespace_directory(dst)
        if dstdir == None:
            sys.stderr.write("wmlmove: invalid namespace %s\n" % dstdir)
            sys.exit(1)
  
    # Cross-reference all files.
    cref = CrossRef(scopelist())

    # Filter reference information on all files referenced in the source .cfgs
    srcrefs = cref.subtract(srclist)

    # List all relevant resources and their other references.
    if listem:
        for (name, defloc) in srcrefs.fileref.items():
            print "Resource %s is used in %d files:" % \
                  (defloc, len(defloc.references))
            defloc.dump_references()

    # Generate the actual move commands
    print "# Generated script from", " ".join(sys.argv[1:])

    if not delete and not revert:
        print '''
replace()
# Replace a specified string with another in any number of files
{
    left="$1"; right="$2"; shift; shift;

    for file in $*
    do
	if grep "$left" $file >/dev/null 2>&1
	then
	    overwrite $file sed "s@$left@$right@g" $file
	fi
    done
}

overwrite()
# Replace a file with itself filtered by a command
{
    opath=$PATH
    PATH=/bin:/usr/bin

    file=$1; shift
    new=/tmp/over$$; old=/tmp/under$$
    trap \'rm -f $new $old ; exit 1\' 1 2 15

    if PATH=$opath "$@" >$new
    then
            cp $file $old		# save the old file
            trap "" 1 2 15		# We are committed; ignore signals
            cp $new $file
    else
            echo "overwrite: $1 failed, $file unchanged" 1 >&2
            exit 1
    fi
    rm -f $new $old
}

'''
    print "chdir", os.getcwd()
    if delete:
        print "# Image deletions:"
        for (name, defloc) in srcrefs.fileref.items():
                for namespace in map(directory_namespace, srclist):
                    if namespace_member(name, namespace) and cref.refcount(name) == 0:
                        if revert:
                            print vcundelete(name)
                        else:
                            print vcdelete(name)
                        break
        print ""
        print "# .cfg deletions"
        for filename in srclist:
            if revert:
                print vcundelete(filename)
            else:
                print vcdelete(filename)
    else:
        if iclass == None:
            print "# Defaulting image subclass to 'monsters', use -i to set it."
            iclass = 'monsters'
        print "# Image moves:"
        for (name, defloc) in srcrefs.fileref.items():
            source = directory_namespace(name)
            target = resolve_unit_image(dst, iclass, os.path.basename(name))
            if revert:
                if not namespace_member(name, source):
                    print vcunmove(name, target)
            else:
                if not namespace_member(name, dst):
                    print vcmove(name, target)
        print ""
        print "# .cfg moves and transformations"
        for filename in srclist:
            source = directory_namespace(filename)
            target = resolve_unit_cfg(dst, os.path.basename(filename))
            if revert:
                if not namespace_member(filename, source):
                    print vcunmove(filename, target)
            else:
                if not namespace_member(filename, dst):
                    print vcmove(filename, target)
                    if iclass:
                        print "replace 'units/' 'units/%s/' %s" % (iclass, target)
                        if dst == "core":
                            print "replace '#textdomain wesnoth.*' '#textdomain wesnoth' %s" % target
                    else:
                        print "echo 'Warning: text domain will require manual change.'"

# wmlmove ends here
